#!/usr/bin/env python3

# Copyright (c) 2020-2021 Fpemud <fpemud@sina.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import os
import re
import enum
import pathlib
from subprocess import CalledProcessError
from .util import WindowsRegistryFileReader, WindowsRegistryFileWriter
from ._util import Util


class WineReg:

    class Query(enum.Enum):
        HAS_KEY = enum.auto()
        GET_SUBKEYS = enum.auto()
        GET_VALUES = enum.auto()
        GET_VALUE = enum.auto()

    def __init__(self, parent):
        '''
        This object represents the functions of "wine reg" command.
        Don't read or modify registry files directly because wineprefix may be used by multiple programs simutanously, there're race conditions.

        Parameters
        ----------
        parent: WinePrefix
            parent object
        '''

        self._parent = parent

    def dispose(self):
        del self._parent

    def add(self, key, default_value=False, value_name=None, value_type=None, data=None):
        self._parent._objAltReg.save()

        if default_value:
            assert value_name is None
            assert self._is_data_valid(value_type, data)
        elif value_name is not None:
            assert not default_value
            assert value_name not in ["(Default)", "*"]    # wine reg query show default value as "(Default)"; we use "*" to represent all values
            assert self._is_data_valid(value_type, data)
        else:
            assert value_type is None
            assert data is None

        if default_value:
            self._parent._execute(["wine", "reg", "add", key, "/ve", "/t", value_type, "/d", self._data2rawData(value_type, data), "/f"])
        elif value_name is not None:
            self._parent._execute(["wine", "reg", "add", key, "/v", value_name, "/t", value_type, "/d", self._data2rawData(value_type, data), "/f"])
        else:
            self._parent._execute(["wine", "reg", "add", key, "/f"])

    def delete(self, key, default_value=False, value_name=None):
        self._parent._objAltReg.save()

        # key does not exist, do nothing
        try:
            self._parent._execute(["wine", "reg", "query", key])
        except CalledProcessError as e:
            if e.returncode == 1 and "reg: Unable to find the specified registry key" in e.output:
                return
            raise

        # do delete
        if default_value:
            assert value_name is None
            self._parent._execute(["wine", "reg", "delete", key, "/ve", "/f"])
        elif value_name is not None:
            if value_name == "*":
                self._parent._execute(["wine", "reg", "delete", key, "/va", "/f"])
            else:
                self._parent._execute(["wine", "reg", "delete", key, "/v", value_name, "/f"])
        else:
            self._parent._execute(["wine", "reg", "delete", key, "/f"])

    def copy(self, key1, key2):
        self._parent._objAltReg.save()

        raise NotImplementedError()

    def query(self, qtype, key, **kwargs):
        self._parent._objAltReg.save()

        if qtype == self.Query.HAS_KEY:
            try:
                self._parent._execute(["wine", "reg", "query", key])
            except CalledProcessError as e:
                if e.returncode == 1 and "reg: Unable to find the specified registry key" in e.output:
                    return False
                raise
            return True

        if qtype == self.Query.GET_SUBKEYS:
            ret = []
            for line in self._parent._execute(["wine", "reg", "query", key, "/s"]).split("\n"):
                if len(line) > len(key) and line.startswith(key):
                    subkey = line[len(key) + 1:]
                    if "\\" not in subkey or kwargs.get("recursive", False):
                        ret.append(subkey)
            return ret

        if qtype == self.Query.GET_VALUES:
            ret = []
            bStart = False
            for line in self._parent._execute(["wine", "reg", "query", key]).split("\n"):
                if line == key:
                    bStart = True
                    continue
                if bStart:
                    m = re.fullmatch(r"\s+(\S+)\s+(\S+)(\s+(.*))?", line)
                    if m is not None:
                        if m.group(3) is not None:
                            rawData = m.group(4)
                        else:
                            rawData = ""
                        ret.append((m.group(1), m.group(2), self._rawData2data(m.group(2), rawData)))
                        continue
                    elif line == "":
                        break
                    else:
                        assert False
            return ret

        if qtype == self.Query.GET_VALUE:
            bStart = False
            for line in self._parent._execute(["wine", "reg", "query", key]).split("\n"):
                if line == key:
                    bStart = True
                    continue
                if bStart:
                    m = re.fullmatch(r"\s+(\S+)\s+(\S+)(\s+(.*))?", line)
                    if m is not None:
                        if m.group(1) == kwargs["value_name"]:
                            if m.group(3) is not None:
                                rawData = m.group(4)
                            else:
                                rawData = ""
                            if kwargs.get("value_type", None) is not None:
                                assert m.group(2) == kwargs["value_type"]
                                return self._rawData2data(m.group(2), rawData)
                            else:
                                return (m.group(2), self._rawData2data(m.group(2), rawData))
                    elif line == "":
                        break
                    else:
                        assert False
            return None

        assert False

    def import_file(self, filepath):
        self._parent._objAltReg.save()

        self._parent._execute(["wine", "reg", "import", filepath], printRegFile=filepath)

    def export_file(self, filepath):
        self._parent._objAltReg.save()

        raise NotImplementedError()

    @staticmethod
    def _is_data_valid(value_type, data):
        # _value_types = [
        #     "REG_NONE",
        #     "REG_DWORD_LITTLE_ENDIAN",
        #     "REG_DWORD_BIG_ENDIAN",
        #     "REG_QWORD",
        #     "REG_QWORD_LITTLE_ENDIAN",
        #     "REG_EXPAND_SZ",
        #     "REG_MULTI_SZ",
        #     "REG_BINARY",
        #     "REG_LINK"
        # ]

        if value_type == "REG_DWORD":
            return isinstance(data, int)
        elif value_type == "REG_SZ":
            return isinstance(data, str)
        else:
            assert False

    @staticmethod
    def _data2rawData(value_type, data):
        if value_type == "REG_DWORD":
            return "0x%x" % (data)          # 17: int -> "0x11": str
        elif value_type == "REG_SZ":
            return data
        else:
            assert False

    @staticmethod
    def _rawData2data(value_type, raw_data):
        if value_type == "REG_DWORD":
            return int(raw_data, 16)        # "0x11": str -> 17: int
        elif value_type == "REG_SZ":
            return raw_data
        else:
            assert False


class AlternativeReg:

    def __init__(self, parent):
        self._parent = parent
        self._obj = parent.get_wine_reg()               # FIXME
        self._writer = WindowsRegistryFileWriter()

    def dispose(self):
        assert self._writer.is_empty()

        del self._writer
        del self._obj
        del self._parent

    @property
    def changed(self):
        return not self._writer.is_empty()

    def has_key(self, key):
        assert key is not None

        return self._obj.query(self._obj.Query.HAS_KEY, key)

    def has_value(self, key, value_name, value_type=None):
        assert key is not None and value_name is not None

        assert self._obj.query(self._obj.Query.HAS_KEY, key)

        for value_name2, value_type2, data2 in self._obj.query(self._obj.Query.GET_VALUES, key):
            if value_name == value_name2:
                if value_type is not None:
                    assert value_type == value_type2
                return True
        return False

    def list_subkeys(self, key, recursive=False):
        assert key is not None

        assert self._obj.query(self._obj.Query.HAS_KEY, key)

        return self._obj.query(self._obj.Query.GET_SUBKEYS, key, recurisve=recursive)

    def list_values(self, key):
        assert key is not None

        assert self._obj.query(self._obj.Query.HAS_KEY, key)

        ret = {}
        for value_name, value_type, data in self._obj.query(self._obj.Query.GET_VALUES, key):
            ret[value_name] = (value_type, data)
        return ret

    def add_key(self, key):
        assert key is not None

        # FIXME: also add parent key
        self._writer.add_key(key)

    def delete_key(self, key):
        assert key is not None

        self._obj.delete(key)

    def get_value(self, key, value_name, value_type=None, fallback_value_type=None, fallback_value=None):
        assert key is not None and value_name is not None

        if fallback_value is None:
            assert fallback_value_type is None
        else:
            if value_type is not None:
                assert fallback_value_type is None or fallback_value_type == value_type
            else:
                assert fallback_value_type is not None

        assert self._obj.query(self._obj.Query.HAS_KEY, key)

        ret = self._obj.query(self._obj.Query.GET_VALUE, key, value_name=value_name, value_type=value_type)
        if ret is not None:
            return ret

        if fallback_value is not None:
            if value_type is not None:
                return fallback_value
            else:
                return (fallback_value_type, fallback_value)

        assert False

    def add_value(self, key, value_name, value_type, data):
        assert key is not None and value_name is not None and value_type is not None and data is not None

        self._writer.add_value(key, value_name, value_type, data)

    def set_value(self, key, value_name, value_type, data):
        assert key is not None and value_name is not None and value_type is not None and data is not None

        # assert self.has_value(key, value_name, value_type)
        self._writer.add_value(key, value_name, value_type, data)

    def delete_value(self, key, value_name):
        assert key is not None and value_name is not None

        self._obj.delete(key, value_name=value_name)

    def import_text(self, text):
        self._writer.add_text(text)

    def import_file(self, filepath, format):
        if format == "unix":
            self._writer.add_text(pathlib.Path(filepath).read_text())
        elif format == "windows":
            self._writer.add_text(WindowsRegistryFileReader(filepath).get_text())
        else:
            assert False

    def export_text(self):
        raise NotImplementedError()

    def export_file(self, filepath):
        raise NotImplementedError()

    def save(self):
        if self._writer.is_empty():
            return False

        fullfn = os.path.join(self._parent._path, "temp.reg")
        self._writer.save(fullfn)
        self._parent._execute(["wine", "regedit", "/C", fullfn], printRegFile=fullfn)
        Util.forceDelete(fullfn)

        self._writer = WindowsRegistryFileWriter()
        return True
